package cc.magicjson.scenario.implement.module.jdbc.template.provider;

import cc.magicjson.scenario.implement.module.jdbc.template.exception.SqlLoadException;
import cc.magicjson.scenario.implement.module.jdbc.template.exception.SqlNotFoundException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.xml.XMLIncludeTransformer;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * SQL提供者，集中管理SQL语句
 */
@Slf4j
@Component
public class SqlProvider {


    private Configuration configuration;

    private final Map<String, SqlSource> sqlSourceCache = new ConcurrentHashMap<>();
    private XMLLanguageDriver languageDriver;

    @Value("${mybatis.id-label-fragments:Selective}")
    private String mybatisIdLabelFragments;

    private Set<String> idLabelFragments;

    @PostConstruct
    public void init() {
        initConfiguration();
        initIdLabelFragments();
        this.languageDriver = new XMLLanguageDriver();
        loadMybatisMappings();
    }

    private void initConfiguration() {
        configuration = new Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCallSettersOnNulls(true);
    }

    private void initIdLabelFragments() {
        if (!StringUtils.hasText(mybatisIdLabelFragments)) {
            idLabelFragments = Collections.emptySet();
            return;
        }

        idLabelFragments = Arrays.stream(mybatisIdLabelFragments.split(","))
            .filter(StringUtils::hasText)
            .map(String::trim)
            .collect(Collectors.toSet());
    }

    private void loadMybatisMappings() {
        try {
            Resource[] resources = new PathMatchingResourcePatternResolver()
                .getResources("classpath*:mapper/**/*.xml");

            for (Resource resource : resources) {
                parseMapper(resource);
            }
        } catch (IOException e) {
            throw new SqlLoadException("Failed to load MyBatis mappings", e);
        }
    }

    private void parseMapper(Resource resource) {
        try (InputStream inputStream = resource.getInputStream()) {
            XPathParser parser = new XPathParser(
                inputStream,
                true,
                configuration.getVariables(),
                new XMLMapperEntityResolver()
            );

            XNode mapper = parser.evalNode("/mapper");
            if (mapper == null) {
                return;
            }

            String namespace = mapper.getStringAttribute("namespace");

            // 先解析SQL片段
            parseSqlFragments(mapper);

            // 解析SQL语句
            parseStatements(mapper, namespace);

        } catch (Exception e) {
            throw new SqlLoadException("Failed to parse mapper: " + resource.getFilename(), e);
        }
    }

    private void parseSqlFragments(XNode mapper) {
        List<XNode> sqlNodes = mapper.evalNodes("sql");
        String namespace = mapper.getStringAttribute("namespace");

        for (XNode sqlNode : sqlNodes) {
            String id = sqlNode.getStringAttribute("id");
            String fragmentId = namespace + "." + id;
            configuration.getSqlFragments().put(fragmentId, sqlNode);
            log.debug("Loaded SQL fragment: {} -> {}", fragmentId, sqlNode);
        }
    }

    private void parseStatements(XNode mapper, String namespace) {
        List<XNode> statements = mapper.evalNodes("select|insert|update|delete");
        for (XNode statement : statements) {
            String id = statement.getStringAttribute("id");
            String key = namespace + "." + id;

            if (!containsAnyIdLabelFragment(id)) {
                continue;
            }
            try {
                // 如果是insert句，检查是否有selectKey
                if ("insert".equals(statement.getName())) {
                    XNode selectKeyNode = statement.evalNode("selectKey");
                    if (selectKeyNode != null) {
                        // 创建组合的SqlSource
                        SqlSource sqlSource = createInsertWithSelectKeySqlSource(statement, selectKeyNode);
                        sqlSourceCache.put(key, sqlSource);
                        continue;
                    }
                }

                // 创建 MapperBuilderAssistant
                MapperBuilderAssistant builderAssistant = new MapperBuilderAssistant(
                    configuration,
                    mapper.getNode().getOwnerDocument().getDocumentURI()
                );
                builderAssistant.setCurrentNamespace(namespace);

                // 处理 include 标签
                XMLIncludeTransformer includeParser = new XMLIncludeTransformer(
                    configuration,
                    builderAssistant
                );
                includeParser.applyIncludes(statement.getNode());

                // 创建 SqlSource
                SqlSource sqlSource = createSqlSource(statement, builderAssistant);
                sqlSourceCache.put(key, sqlSource);
                log.debug("Loaded statement: {}", key);
            } catch (Exception e) {
                log.error("Error parsing statement: {} in namespace: {}", id, namespace, e);
                throw new SqlLoadException("Error parsing statement: " + key, e);
            }
        }
    }

    private boolean containsAnyIdLabelFragment(String id) {
        if (idLabelFragments.isEmpty()) {
            return true;
        }
        for (String fragment : idLabelFragments) {
            if (id.contains(fragment)) {
                return true;
            }
        }
        return false;
    }

    private SqlSource createSqlSource(XNode node, MapperBuilderAssistant builderAssistant) {
        try {
            // 使用 XMLLanguageDriver 创建 SqlSource
            return languageDriver.createSqlSource(
                configuration,
                node,
                builderAssistant.getCurrentNamespace().getClass()
            );
        } catch (Exception e) {
            log.error("Error creating SqlSource for node: {}", node.getStringAttribute("id"), e);
            throw new SqlLoadException("Error creating SqlSource", e);
        }
    }

    public String getSql(String key, Object parameterObject) {
        try {
            SqlSource sqlSource = sqlSourceCache.get(key);
            if (sqlSource == null) {
                throw new SqlNotFoundException("SQL not found for key: " + key);
            }

            Object processedParam = processParameterObject(parameterObject);
            BoundSql boundSql = sqlSource.getBoundSql(processedParam);
            String sql = boundSql.getSql();

            log.debug("Generated SQL for key [{}]: {}", key, sql);
            return sql;
        } catch (Exception e) {
            log.error("Failed to get SQL for key: {}", key, e);
            throw new SqlLoadException("Failed to get SQL for key: " + key, e);
        } catch (SqlNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private Object processParameterObject(Object parameterObject) {
        if (parameterObject == null) {
            return null;
        }

        if (parameterObject instanceof Collection) {
            Map<String, Object> map = new HashMap<>();
            map.put("list", parameterObject);
            map.put("collection", parameterObject);
            return map;
        }

        if (parameterObject.getClass().isArray()) {
            Map<String, Object> map = new HashMap<>();
            map.put("array", parameterObject);
            return map;
        }

        return parameterObject;
    }

    // 添加用于调试的方法
    public void dumpSqlFragments() {
        log.info("Dumping all SQL fragments:");
        configuration.getSqlFragments().forEach((key, value) ->
            log.info("{} -> {}", key, value.toString())
        );
    }

    // 用于获取参数映射
    public List<ParameterMapping> getParameterMappings(String key, Object parameterObject) throws SqlNotFoundException {
        SqlSource sqlSource = sqlSourceCache.get(key);
        if (sqlSource == null) {
            throw new SqlNotFoundException("SQL not found for key: " + key);
        }
        return sqlSource.getBoundSql(parameterObject).getParameterMappings();
    }

    /**
     * 获取SQL参数信息
     * @param key SQL键
     * @param parameterObject 参数对象
     * @return 参数信息列表
     */
    public List<SqlParameterInfo> getSqlParameterInfos(String key, Object parameterObject) throws SqlNotFoundException {
        SqlSource sqlSource = sqlSourceCache.get(key);
        if (sqlSource == null) {
            throw new SqlNotFoundException("SQL not found for key: " + key);
        }

        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        return parameterMappings.stream()
            .map(mapping -> new SqlParameterInfo(
                mapping.getProperty(),
                mapping.getJavaType(),
                mapping.getMode()
            ))
            .collect(Collectors.toList());
    }

    private SqlSource createInsertWithSelectKeySqlSource(XNode insertNode, XNode selectKeyNode) {
        String keyProperty = selectKeyNode.getStringAttribute("keyProperty");
        boolean executeBefore = "BEFORE".equals(selectKeyNode.getStringAttribute("order", "AFTER"));
        
        StringBuilder sqlBuilder = new StringBuilder();
        
        if (executeBefore) {
            // BEFORE: 先执行selectKey，再执行insert
            sqlBuilder.append(selectKeyNode.getStringBody())
                     .append("; ")
                     .append(insertNode.getStringBody());
        } else {
            // AFTER: 先执行insert，再执行selectKey
            sqlBuilder.append(insertNode.getStringBody())
                     .append("; ")
                     .append(selectKeyNode.getStringBody());
        }
        
        // 使用原始的languageDriver创建SqlSource
        return languageDriver.createSqlSource(
            configuration,
            sqlBuilder.toString(),
            Object.class
        );
    }

}
